package ece.edt;

import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.io.StringReader;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.SimpleTimeZone;
import java.util.TimeZone;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;

import ece.edt.exceptions.ScheduleException;

public class Schedule implements Serializable{
	private static final Logger log = Logger.getLogger(Schedule.class);
	private static final long serialVersionUID = 5322091655347574909L;

	class ScheduleHandler extends DefaultHandler {
		private Course c = null;
		private String currentElement = "";
		private boolean addCourse = false;
		
		private Date weekStart;
		private Calendar calendar = Calendar.getInstance();
		
		public ScheduleHandler(){
		}
		
		public void startDocument() {
			if(courses == null){
				courses = new ArrayList<Course>();
			}
		}

		public void endDocument() {
		}

		public void startElement(String uri, String localName, String qname, Attributes attr) {
			currentElement = "";
			
			if(localName.equals("LISTECOURS")){
				int edtWeek = Integer.parseInt(attr.getValue("N"));
				int realWeek = (edtWeek+35)%52;

				
				calendar.set(Calendar.WEEK_OF_YEAR, realWeek);
				calendar.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
				calendar.set(Calendar.HOUR_OF_DAY, 8);
				calendar.set(Calendar.MINUTE, 0);
				calendar.set(Calendar.SECOND, 0);
				calendar.set(Calendar.MILLISECOND, 0);
				weekStart = calendar.getTime();
				addCourse = true;
			}
			
			if(addCourse){
				if(localName.equals("Cours")){
					c = new Course();
					courses.add(c);
				}else if(localName.equals("Contenu")){
					String value = attr.getValue("L");
					
					switch(Integer.parseInt(attr.getValue("G"))){
						case 0 : c.setName(value); break;
						case 1 : c.setProfessor(value); break;
						case 2 : c.setAudience(value); break;
						case 3 : c.setRoom(value); break;
						case 7 : c.setType(value); break;
					}
				}else if(localName.equals("DEBUT")){
					currentElement = "DEBUT";
				}else if(localName.equals("FIN")){
					currentElement = "FIN";
				}
			}
		}
		
		public void endElement(String uri, String localName, String qname) {
			if(localName.equals("LISTECOURS")){
				addCourse = false;
			}
		}
		
		public void characters(char[] ch, int start, int length) {
			if(addCourse){
				if(currentElement.equals("DEBUT")){
					int quarter = Integer.parseInt(new String(ch, start, length));
					c.setStart(new Date(weekStart.getTime() + 86400000*(quarter/56) + (quarter%56)*900000));
				}else if(currentElement.equals("FIN")){
					int quarter = Integer.parseInt(new String(ch, start, length))+1;
					c.setEnd(new Date(weekStart.getTime() + 86400000*(quarter/56) + (quarter%56)*900000));
				} 
			}
		}
	}

	static class Course implements Comparable<Course>, Serializable {
		private static final long serialVersionUID = 8083120788147131354L;
		private Date start;
		private Date end;
		private String name;
		private String professor;
		private String audience;
		private String room;
		private String type;
		

		public String getDuration(){
			long duration = (end.getTime() - start.getTime()) / (1000*60);
			int hours = (int)(duration/60);
			int minutes = (int)(duration - hours*60);
			
			return "" + hours + "h" + ((minutes != 0) ? minutes : "");
		}
		
		public Date getStart() {
			return start;
		}
		public void setStart(Date start) {
			this.start = start;
		}
		public Date getEnd() {
			return end;
		}
		public void setEnd(Date end) {
			this.end = end;
		}
		public String getName() {
			return name;
		}
		public void setName(String name) {
			this.name = name;
		}
		public String getProfessor() {
			return professor;
		}
		public void setProfessor(String professor) {
			this.professor = professor;
		}
		public String getAudience() {
			return audience;
		}
		public void setAudience(String audience) {
			this.audience = audience;
		}
		public String getRoom() {
			return room;
		}
		public void setRoom(String room) {
			this.room = room;
		}
		public String getType() {
			return type;
		}
		public void setType(String type) {
			if(type != null) {
				this.type = type.endsWith("_HRS") ? type.substring(0, type.indexOf("_HRS")) : type;
			}
		}
		
		public String getUID(){
			SimpleDateFormat sdf = new SimpleDateFormat();
			return DigestUtils.md5Hex(sdf.format(start)+name+room);
		}
		
		public int compareTo(Course c) {
			return start.compareTo(c.getStart());
		}
		
		/**
		 * @see java.lang.Object#equals(java.lang.Object)
		 */
		@Override
		public boolean equals(Object obj) {
			if (this == obj) {
				return true;
			} else if (obj instanceof Course){
				return start.equals(((Course) obj).getStart());
			} else {
				return false;
			}
		}
		
		/**
		 * @see java.lang.Object#hashCode()
		 */
		@Override
		public int hashCode() {
			return start.hashCode();
		}
	}
	private List<Course> courses;
	private String user;
	
	public Schedule(String u) {
		user = u;
	}
	
	public void addWeek(String xml) throws ScheduleException{
		try {
			XMLReader parser = XMLReaderFactory.createXMLReader();
			parser.setContentHandler(new ScheduleHandler());
			parser.parse(new InputSource(new StringReader(xml)));
		} catch (SAXException e) {
			throw new ScheduleException(e.getMessage());
		} catch (IOException e) {
			throw new ScheduleException(e.getMessage());
		}
	}
	
	public String exportAsRSS() throws ScheduleException{
		StringBuilder s = new StringBuilder();
		
		Collections.sort(courses);
		
		s.append(
		"<?xml version=\"1.0\" encoding=\"utf-8\" ?>" + "\n" +
		"<rss version=\"2.0\">" + "\n" +
			"<channel>" + "\n" +
				"<title><![CDATA[EDT ECE - " + user + "]]></title>" + "\n" +
				"<link><![CDATA[http://edt.ece.fr]]></link>" + "\n" +
				"<description><![CDATA[Emploi du temps ECE pour " + user + "]]></description>" + "\n" +
				"<language><![CDATA[fr_FR]]></language>" + "\n" +
				"<pubDate><![CDATA[" + getGMTDate() + "]]></pubDate>" + "\n" +
				"<lastBuildDate><![CDATA[" + getGMTDate() + "]]></lastBuildDate>" + "\n" +
				"<generator><![CDATA[EDTGrabber]]></generator>" + "\n" +
				"<managingEditor><![CDATA[elfeshurikn@free.fr]]></managingEditor>" + "\n" +
				"<webMaster><![CDATA[elfeshurikn@free.fr]]></webMaster>" + "\n" +
				"<ttl><![CDATA[15]]></ttl>" + "\n");
				
				for(int i = 0; i < courses.size(); i++){
					Course c = courses.get(i);
					if(c.getEnd().after(new Date())){
						s.append(
				"<item>" + "\n" +
					"<title><![CDATA[" + getGMTDate(c.getStart()) + " : (" + c.getDuration() + ") " + c.getName() + " (" + c.getType() + ") - " + c.getRoom() + "]]></title>" + "\n" +
					"<link><![CDATA[http://edt.ece.fr]]></link>" + "\n" +
					"<description><![CDATA[" + c.getName() + " (" + c.getType() + ") " + "\n" + c.getProfessor() + "\n" + c.getRoom() + "]]></description>" + "\n" +
					"<pubDate><![CDATA[" + getGMTDate() + "]]></pubDate>" + "\n" +
					"<guid><![CDATA[" + c.getUID() + "]]></guid>" + "\n" +
				"</item>" + "\n");
					}
				}
				
				s.append(
			"</channel>" + "\n" +
		"</rss>");
		
		return s.toString();
	}
	
	public String exportAsIcal(boolean outlook2003) throws ScheduleException{
		StringBuilder s = new StringBuilder();
		//String eol = System.getProperty("line.separator"); 
		String eol = "\r\n";
		s.append(
		"BEGIN:VCALENDAR" + eol +
		"CALSCALE:GREGORIAN" + eol +
		"X-WR-CALNAME:ECE-" + user + eol +

		"METHOD:PUBLISH" + eol +
		"VERSION:2.0" + eol);
		
		if(!outlook2003){
			s.append(
		"BEGIN:VTIMEZONE" + eol +
		"TZID:Europe/Paris" + eol +
		"BEGIN:DAYLIGHT" + eol +
		"TZOFFSETFROM:+0100" + eol +
		"TZOFFSETTO:+0200" + eol +
		"DTSTART:19810329T020000" + eol +
		"RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU" + eol +
		"TZNAME:CEST" + eol +
		"END:DAYLIGHT" + eol +
		"BEGIN:STANDARD" + eol +
		"TZOFFSETFROM:+0200" + eol +
		"TZOFFSETTO:+0100" + eol +
		"DTSTART:19961027T030000" + eol +
		"RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU" + eol +
		"TZNAME:CET" + eol +
		"END:STANDARD" + eol +
		"END:VTIMEZONE" + eol);
		}
		
		for(Course c : courses){
			s.append(
		"BEGIN:VEVENT" + eol +
		"LOCATION:" + c.getRoom() + eol +
		"DTSTAMP:" + iCalGMDate(c.getStart()) + eol +
		"UID:Ical" + c.getUID() + eol +
		"DTSTART;TZID=Europe/Paris:" + iCalDate(c.getStart()) + eol +
		"DTEND;TZID=Europe/Paris:" + iCalDate(c.getEnd()) + eol +
		"SUMMARY:" + c.getName() + " (" + c.getType() + ") - " + c.getRoom() + eol +
		"DESCRIPTION:" + c.getName() + " (" + c.getType() + ") " + "\\n" + c.getProfessor() + "\\n" + c.getRoom() + eol +
		"END:VEVENT" + eol);
		}
		
		s.append(
		"END:VCALENDAR");
		
		return s.toString();
	}
	
	public String exportAsText() throws ScheduleException{
		StringBuilder s = new StringBuilder();
		
		Collections.sort(courses);
		
		s.append("ECE Schedule for " + user + "\n\n");
		
		for(int i = 0; i < courses.size(); i++){
			Course c = courses.get(i);
			if(c.getEnd().after(new Date())){
				s.append(getGMTDate(c.getStart()) + " : (" + c.getDuration() + ") " + c.getName() + " (" + c.getType() + ") - " + c.getRoom() + " - " + c.getProfessor() + "\n");
			}		
		}
		return s.toString();
	}
	
	public void saveAsIcal(boolean outlook2003) throws ScheduleException{
		try {
			FileUtils.writeStringToFile(new File(user + ".ics"), exportAsIcal(outlook2003));
			System.out.println("EDT successfully grabbed to " + user + ".ics !");
		} catch (IOException e) {
			throw new ScheduleException(e.getMessage());
		}
	}
	
	public String iCalGMDate(Date d){
		SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
		sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
		Calendar c = Calendar.getInstance(new SimpleTimeZone(0, "GMT"));
		sdf.setCalendar(c);  
		return sdf.format(d);
	}
	
	public String iCalDate(Date d){
		SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
		return sdf.format(d);
	}
	
	public String getGMTDate(){
		Calendar c = Calendar.getInstance(new SimpleTimeZone(0, "GMT"));

		SimpleDateFormat sdf = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z", Locale.US);
		sdf.setCalendar(c);
		return sdf.format(c.getTime());
	}
	
	public String getGMTDate(Date d){
		SimpleDateFormat sdf = new SimpleDateFormat("EEE d MMM HH:mm");
		return sdf.format(d);
	}
}
